using AnimCollections;
using System;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;

namespace DA.AnimGraph {
    public unsafe struct AnimGraphController : IDisposable {
        private static string outputName = "anim_output";

        public int graphCount => animGraphs.Count;

        private PlayableGraph playableGraph;
        private AnimationPlayableOutput playableOutput;

        private SparsePtrList<AnimGraph> animGraphs;
        private EvaluateContext evaluateContext;

        public int rootIndex { get; private set; }
        AnimGraph* rootGraph => animGraphs[rootIndex];

        CurveBuffer<CurveSample> curveBuffer;
        UpstreamData upstream;

        public AnimGraphController(Animator animator, string name) {
            curveBuffer = new();
            curveBuffer.Init();

            animGraphs = new(4, Allocator.Persistent);
            playableGraph = PlayableGraph.Create(name);
            //playableGraph.SetTimeUpdateMode(DirectorUpdateMode.Manual);
            playableOutput = AnimationPlayableOutput.Create(playableGraph, outputName, animator);
            evaluateContext = new EvaluateContext();
            upstream = new UpstreamData(4);
            rootIndex = -1;
        }

        public void Dispose() {
            upstream.Dispose();

            for (int i = 0; i < animGraphs.MaxCount; i++) {
                if (animGraphs[i] != null) {
                    animGraphs[i]->Dispose();
                }
            }
            animGraphs.Dispose();

            if (playableGraph.IsValid()) playableGraph.Destroy();
            curveBuffer.Dispose();
        }

        /// <summary>
        /// 添加一个 AnimGraph，并且设为 root
        /// </summary>
        /// 比添加第二个多出的步骤就是要设置到playableOutput上
        public int SetupRootGraph(AnimGraph* newGraph, in BuildContext buildContext) {
            // 替换原先的 rootGraph，先接上新的再删除旧的
            int oldIndex = -1;
            if (rootIndex >= 0) {
                oldIndex = rootIndex;
                rootIndex = -1;
            }

            var index = AddGraphAndBuildPlayable(newGraph, buildContext);
            playableOutput.SetSourcePlayable(newGraph->rootPlayable);
            rootIndex = index;
            if (oldIndex >= 0) {
                RemoveGraph(oldIndex);
            }
            return index;
        }

        /// <summary>
        /// 将一个新 AnimGraph 加入 AnimGraphController
        /// AnimGraph 需要已经初始化完成
        /// 此接口不负责连接
        /// </summary>
        /// <returns>此 graph 的序号</returns>
        public int AddGraphAndBuildPlayable(AnimGraph* newGraph, in BuildContext buildContext) {
            Debug.Assert(newGraph->isValid, "cannot add invalid graph");
            newGraph->InitNode();
            newGraph->BuildPlayableGraph(playableGraph, buildContext);
            int index = animGraphs.Add(newGraph);
            newGraph->graphArrayInfo = new GraphArrayInfo(index);
            return index;
        }

        /// <summary>
        /// 将一个新 AnimGraph 加入 AnimGraphController，并且连接到指定 slot
        /// </summary>
        /// <returns>此 graph 的序号</returns>
        public int AddGraphAndConnectToSlot(AnimGraph* newGraph, AUID slotName, in BuildContext buildContext) {
            int index = AddGraphAndBuildPlayable(newGraph, buildContext);
            ConnectGraphToSlot(index, slotName);
            return index;
        }

        public int FindGraph(AUID uuid) {
            for (int i = 0; i < animGraphs.MaxCount; i++) {
                if (animGraphs[i] != null && animGraphs[i]->isValid && animGraphs[i]->graphIdentification.uuid == uuid) {
                    return i;
                }
            }
            return -1;
        }
        
        /// <summary>
        /// 编辑器、debug用，运行时使用不安全
        /// </summary>
        public AnimGraph* GetGraph(int index) {
            return animGraphs[index];
        }

        /// <summary>
        /// 将一个 graph 接入 slot
        /// </summary>
        /// <param name="graphIndex">graph 的序号，可以在添加 graph 的时候得到</param>
        /// <param name="slotName">需要接入 slot 的名字</param>
        /// <returns>被顶替掉的原来此 slot 接入的 graph 序号，如要移除此 graph 可以使用 RemoveGraph()，失败为 -1</returns>
        public int ConnectGraphToSlot(int graphIndex, AUID slotName) {
            var graph = animGraphs[graphIndex];
            int oldGraphIndex = -99; // 初始值为一个没有被占用的值

            if (graph->graphArrayInfo.slotName == slotName) {
                // 已经接入目标 slot，可能需要重新激活
                var parentGraph = animGraphs[graph->graphArrayInfo.attachedGraphIndex];
                parentGraph->ConnectSlot(slotName, graph, out oldGraphIndex); //这里预期是返回-2，代表原先就已经连接，会重新激活
                return oldGraphIndex;
            }
            if (graph->graphArrayInfo.attachedGraphIndex != -1) {
                // 已经接入别的 slot，先断开
                DisconnectGraph(graphIndex);
            }

            //bool isConnected = false;
            int parentGraphIndex = -99;
            for (int i = animGraphs.MaxCount - 1; i >= 0; --i) {
                if (i == graphIndex || (animGraphs[i] != null && !animGraphs[i]->isValid)) continue;
                if (animGraphs[i]->ConnectSlot(slotName, graph, out oldGraphIndex)) {
                    //isConnected = true;
                    parentGraphIndex = i;
                    break;
                }
            }
            if (oldGraphIndex >= -1) {
                // 连接成功
                graph->graphArrayInfo.attachedGraphIndex = parentGraphIndex;
                graph->graphArrayInfo.slotName = slotName;

                if (oldGraphIndex != -1) {
                    // 连接成功且发生了替换
                    var oldGraph = animGraphs[oldGraphIndex];
                    oldGraph->graphArrayInfo.attachedGraphIndex = -1;
                    oldGraph->graphArrayInfo.slotName = default;
                }
            }
            //RemoveGraph(oldGraphIndex);
            return oldGraphIndex;
        }

        public void DisconnectGraph(int graphIndex) {
            var graph = animGraphs[graphIndex];
            var oldAttachedGraph = animGraphs[graph->graphArrayInfo.attachedGraphIndex];
            oldAttachedGraph->DisconnectSlot(graph->graphArrayInfo.slotName, graph);

            graph->graphArrayInfo.attachedGraphIndex = -1;
            graph->graphArrayInfo.slotName = default;
        }

        /// <summary>
        /// 卸载 graph，会递归的卸载子级
        /// </summary>
        /// <param name="index"></param>
        public void RemoveGraph(int index) {
            if (index == -1) return;
            var iter = animGraphs[index]->GetSlotsEnumerator();
            while (iter.MoveNext()) {
                var (_, g0, g1) = iter.Current;
                RemoveGraph(g0);
                RemoveGraph(g1);
            }

            var graph = animGraphs[index];
            graph->Dispose();
            animGraphs.RemoveAt(index);
        }

        public void Play() {
            playableGraph.Play();
        }

        public unsafe void UpdateBlackboardAddress<T>(in T memorySlice) where T : unmanaged {
            fixed (T* addr = &memorySlice) {
                var baseAddress = (ulong)addr;
                for (int i = 0; i < animGraphs.MaxCount; i++) {
                    if (animGraphs[i] != null && animGraphs[i]->isValid) {
                        animGraphs[i]->UpdateBlackboardAddress(baseAddress);
                    }
                }
            }
        }
        public void UpdateBlackboardAddress(ulong address) {
            for (int i = 0; i < animGraphs.MaxCount; i++) {
                if (animGraphs[i] != null && animGraphs[i]->isValid) {
                    animGraphs[i]->UpdateBlackboardAddress(address);
                }
            }
        }

        public void Evaluate(float deltaTime, out AnimOutputData outputData, in AnimExtraInputData exInputData = default) {
            curveBuffer.Reset();

            evaluateContext.deltaTime = deltaTime;
            ++evaluateContext.evaluateCounter;

            var graphContext = rootGraph->GetGraphContext();
            upstream.Reset();

            var scope = new ScopeContext(1);
            var rootNode = rootGraph->rootNode;
            NodeLink.Evaluate(rootNode, evaluateContext, *graphContext, ref upstream, ref scope);

            SyncGroupProcessor.Process(upstream.SyncGroups, in exInputData.inSyncTime);

            var postScope = new PostScopeContext((CurveBuffer<CurveSample>*)UnsafeUtility.AddressOf(ref curveBuffer));
            NodeLink.PostEvaluate(rootNode, evaluateContext, *graphContext, ref upstream, ref postScope);

            outputData = new();
            outputData.TimelineSlices = upstream.TimelineSlices;
            outputData.CurveSamples = postScope.GetCurveSamples();

            NodeLink.Apply(rootNode);

            //playableGraph.Evaluate(deltaTime);
        }

        public void JobTick(float deltaTime, out AnimOutputData outputData, in AnimExtraInputData exInputData = default) {
            curveBuffer.Reset();

            evaluateContext.deltaTime = deltaTime;
            ++evaluateContext.evaluateCounter;

            var graphContext = rootGraph->GetGraphContext();
            upstream.Reset();

            var scope = new ScopeContext(1);
            var rootNode = rootGraph->rootNode;
            NodeLink.Evaluate(rootNode, evaluateContext, *graphContext, ref upstream, ref scope);

            SyncGroupProcessor.Process(upstream.SyncGroups, in exInputData.inSyncTime);

            var postScope = new PostScopeContext((CurveBuffer<CurveSample>*)UnsafeUtility.AddressOf(ref curveBuffer)); //
            NodeLink.PostEvaluate(rootNode, evaluateContext, *graphContext, ref upstream, ref postScope);

            outputData = new();
            outputData.TimelineSlices = upstream.TimelineSlices;
            outputData.CurveSamples = postScope.GetCurveSamples();
        }

        public void JobApply() {
            var rootNode = rootGraph->rootNode;
            NodeLink.Apply(rootNode);
        }

        public AGTree GetAGTree() {
            if (graphCount == 0) {
                return null;
            }
            AGTree agTree = new();
            agTree.AddToSlot("root", rootIndex, rootGraph->name.ToName(), 0);

            int level = 1;
            int graphIndex = rootIndex;

            void Loop(AGTree agTree, int level, int graphIndex, in SparsePtrList<AnimGraph> animGraphs) {
                foreach (var pair in animGraphs[graphIndex]->slots) {
                    var slotName = pair.Key.ToName();
                    var slot = pair.Value;
                    if (slot.curPortId < 0) {
                        // 空slot
                        agTree.NewSlot(slotName, level, pair.Value.curPortId);
                    } else {
                        agTree.NewSlot(slotName, level, pair.Value.curPortId);
                        // 已填充slot，1个2个都有可能
                        if (slot.curPortId == 0) {
                            if (slot.graphIndexInPort0 >= 0) {
                                agTree.AddToSlot(slotName, slot.graphIndexInPort0, animGraphs[slot.graphIndexInPort0]->name.ToName(), 0);
                                Loop(agTree, level + 1, slot.graphIndexInPort0, in animGraphs);
                            }
                            if (slot.graphIndexInPort1 >= 0) {
                                agTree.AddToSlot(slotName, slot.graphIndexInPort1, animGraphs[slot.graphIndexInPort1]->name.ToName(), 1);
                                Loop(agTree, level + 1, slot.graphIndexInPort1, in animGraphs);
                            }
                        } else {
                            if (slot.graphIndexInPort1 >= 0) {
                                agTree.AddToSlot(slotName, slot.graphIndexInPort1, animGraphs[slot.graphIndexInPort1]->name.ToName(), 1);
                                Loop(agTree, level + 1, slot.graphIndexInPort1, in animGraphs);
                            }
                            if (slot.graphIndexInPort0 >= 0) {
                                agTree.AddToSlot(slotName, slot.graphIndexInPort0, animGraphs[slot.graphIndexInPort0]->name.ToName(), 0);
                                Loop(agTree, level + 1, slot.graphIndexInPort0, in animGraphs);
                            }
                        }
                    }
                }
            }

            Loop(agTree, level, graphIndex, animGraphs);
            return agTree;
        }
    }

}
